package at.fhooe.mcm.logic.network.server;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import at.fhooe.mcm.logic.core.Core;
import at.fhooe.mcm.logic.event.Event;
import at.fhooe.mcm.logic.event.EventListener;
import at.fhooe.mcm.logic.event.EventManager;
import at.fhooe.mcm.logic.logging.ConsoleLoggingService;
import at.fhooe.mcm.logic.network.Peer;
import at.fhooe.mcm.logic.network.events.PeerConnectedEvent;
import at.fhooe.mcm.logic.network.events.PeerDisconnectedEvent;
import at.fhooe.mcm.logic.network.events.PeerInitializeEvent;
import at.fhooe.mcm.logic.network.events.PullEvent;
import at.fhooe.mcm.logic.sensors.SensorManager;
import at.fhooe.mcm.logic.sensors.events.SensorChangedEvent;
import at.fhooe.mcm.logic.sensors.events.SensorCreatedEvent;
import at.fhooe.mcm.logic.sensors.events.SensorDestroyedEvent;
import at.fhooe.mcm.logic.sensors.events.SensorUpdatedEvent;

public class Server extends Thread implements EventListener
{
	public static final int		SERVER_PORT		= 6666;
	public static final String	NAME			= "network.server";
	private static final String	LOGGING_SOURCE	= "Server";

	private Core				m_core;
	private List<Peer>			m_peers			= new ArrayList<Peer>();

	public Server(Core _core)
	{
		m_core = _core;

		EventManager.getInstance(m_core).addListener(this, PeerConnectedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, PeerDisconnectedEvent.TYPE);

		EventManager.getInstance(m_core).addListener(this, SensorUpdatedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, SensorCreatedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, SensorDestroyedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, SensorChangedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, PullEvent.TYPE);
	}

	public void sendToAll(Event _e)
	{
		for (Peer peer : m_peers)
		{
			ByteBuffer buffer = null;
			if ((buffer = serializeEvents(_e)) != null)
			{
				buffer.flip();
				peer.send(buffer);
			}
		}
	}

	public void sendToPeer(Peer _p, Event _e)
	{
		ByteBuffer buffer = null;
		if ((buffer = serializeEvents(_e)) != null)
		{
			buffer.flip();
			_p.send(buffer);
		}
	}

	@Override
	public void run()
	{
		while(true)
		{
			
			ByteBuffer buffer = null;

			for (Peer peer : m_peers)
			{
				while ((buffer = peer.receive()) != null)
				{
					buffer.flip();
					byte[] byts = new byte[buffer.limit()];
					buffer.get(byts);

					deserializeEvents(byts);
					buffer.clear();
				}
			}
			
			try
			{
				Thread.sleep(50);
			}
			catch (InterruptedException e)
			{
				ConsoleLoggingService.getInstance(m_core).logDebug(LOGGING_SOURCE, e.getMessage());
			}
		}
	}

	@Override
	public void handleEvent(Event _event)
	{
		if (_event.getType().equals(PeerConnectedEvent.TYPE))
		{
			PeerConnectedEvent peerConnected = (PeerConnectedEvent) _event;
			m_peers.add(peerConnected.getPeer());
			ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE,
					"Peer connected (" + peerConnected.getPeer().getAddress().toString() + ")");

			PeerInitializeEvent pie = new PeerInitializeEvent(SensorManager.getInstance(m_core).getPublicSensorList());
			sendToPeer(peerConnected.getPeer(), pie);
		}
		else if (_event.isOfType(PeerDisconnectedEvent.TYPE))
		{
			PeerDisconnectedEvent peerDisconnected = (PeerDisconnectedEvent) _event;
			m_peers.remove(peerDisconnected.getPeer());
			ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE,
					"Peer disconnected (" + peerDisconnected.getPeer().getAddress().toString() + ")");
		}
		else if (_event.isOfType(SensorCreatedEvent.TYPE))
		{
			SensorCreatedEvent sensorCreated = (SensorCreatedEvent) _event;

			if (SensorManager.getInstance(m_core).getEntity(sensorCreated.getSensorID()).isPublic())
				sendToAll(_event);
		}
		else if (_event.isOfType(SensorDestroyedEvent.TYPE))
		{
			sendToAll(_event);
		}
		else if (_event.isOfType(SensorUpdatedEvent.TYPE))
		{
			SensorUpdatedEvent sensorUpdated = (SensorUpdatedEvent) _event;

			if (SensorManager.getInstance(m_core).getEntity(sensorUpdated.getSensorID()).isPublic())
				sendToAll(_event);
		}
		else if (_event.isOfType(SensorChangedEvent.TYPE))
		{
			SensorChangedEvent sensorChanged = (SensorChangedEvent) _event;

			if (SensorManager.getInstance(m_core).getEntity(sensorChanged.getSensorID()).isPublic())
				sendToAll(_event);
		}
		else if (_event.isOfType(PullEvent.TYPE))
		{
			SensorManager.getInstance(m_core).pull();
		}
	}

	private ByteBuffer serializeEvents(Event _e)
	{
		try
		{
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);

			oos.writeObject(_e);
			oos.flush();
			oos.close();
			bos.close();

			ByteBuffer buffer = ByteBuffer.allocate(bos.size());
			buffer.put(bos.toByteArray());

			return buffer;
		}
		catch (IOException ex)
		{
			ConsoleLoggingService.getInstance(m_core).logDebug(LOGGING_SOURCE, ex.getMessage());
			return null;
		}
	}

	private void deserializeEvents(byte[] _bytes)
	{
		try
		{
			ByteArrayInputStream bos = new ByteArrayInputStream(_bytes);
			ObjectInputStream oos = new ObjectInputStream(bos);
			Object event = oos.readObject();

			if (!(event instanceof Event))
			{
				ConsoleLoggingService.getInstance(m_core).logDebug(LOGGING_SOURCE, "Transmitted object is not an event");
			}
			else
			{	
				EventManager.getInstance(m_core).enqueueEvent((Event) event);
			}
		}
		catch (IOException e)
		{
			ConsoleLoggingService.getInstance(m_core).logDebug(LOGGING_SOURCE, e.getMessage());
		}
		catch (ClassNotFoundException e)
		{
			ConsoleLoggingService.getInstance(m_core).logDebug(LOGGING_SOURCE, e.getMessage());
		}
	}
}
